Skip to content

OpenClaw 节点设备管理指南

节点(Nodes)是 OpenClaw 连接物理设备的桥梁,支持 Android、iOS、macOS 等设备。本文详解节点配对、设备管理、远程控制等核心功能。

概述

OpenClaw 节点系统支持:

  • 📱 Android 设备连接
  • 🍎 iOS 设备连接
  • 💻 macOS 设备连接
  • 📷 相机控制
  • 📍 位置获取
  • 🔔 通知管理
  • 📸 屏幕录制
  • 🖥️ 远程操作

一、节点架构

1.1 连接模式

┌─────────────────────────────────────────────────────┐
│                  节点连接模式                        │
├─────────────────────────────────────────────────────┤
│  本地 Wi-Fi     │  同一局域网内直连                  │
│  远程 VPS       │  通过公网服务器中转                │
│  Tailscale      │  通过 Tailscale 组网               │
│  手动配置       │  指定 IP 和端口连接                │
└─────────────────────────────────────────────────────┘

1.2 设备配对流程

1. 网关启动设备配对插件
2. 生成配对码/二维码
3. 手机 App 扫描或输入配对码
4. 交换认证信息
5. 建立安全连接
6. 设备上线

二、设备配对

2.1 启用配对插件

yaml
# ~/.openclaw/config.yaml
plugins:
  entries:
    - name: "device-pair"
      enabled: true
      config:
        publicUrl: "https://your-domain.com"  # 公网地址
        port: 8080

2.2 启动配对

bash
# 查看配对状态
openclaw nodes pairing status

# 启动配对(生成二维码)
openclaw nodes pairing start

# 停止配对
openclaw nodes pairing stop

2.3 手动连接

如果自动配对失败,可以手动配置:

javascript
// 获取配对信息
const pairingInfo = await nodes({
  action: 'describe'
})

console.log('配对信息:', pairingInfo)

// 手动连接
await nodes({
  action: 'pairing',
  deviceId: 'manual',
  address: '192.168.1.100',
  port: 8080
})

2.4 实战案例 1:配对诊断工具

javascript
class PairingDiagnostics {
  async check() {
    const report = {
      status: 'unknown',
      issues: [],
      recommendations: []
    }
    
    // 1. 检查插件是否启用
    const pluginEnabled = await this.checkPluginEnabled()
    if (!pluginEnabled) {
      report.issues.push('设备配对插件未启用')
      report.recommendations.push('在 config.yaml 中启用 device-pair 插件')
    }
    
    // 2. 检查公网 URL 配置
    const publicUrl = await this.getPublicUrl()
    if (!publicUrl) {
      report.issues.push('未配置 publicUrl')
      report.recommendations.push('设置 plugins.device-pair.config.publicUrl')
    } else if (!publicUrl.startsWith('https://')) {
      report.issues.push('publicUrl 不是 HTTPS')
      report.recommendations.push('使用 HTTPS 以保证安全')
    }
    
    // 3. 检查端口可访问性
    const portOpen = await this.checkPortAccessibility()
    if (!portOpen) {
      report.issues.push('端口不可访问')
      report.recommendations.push('检查防火墙设置,开放 8080 端口')
    }
    
    // 4. 检查本地网络
    const localNetwork = await this.checkLocalNetwork()
    if (!localNetwork) {
      report.issues.push('本地网络异常')
      report.recommendations.push('确认设备在同一 Wi-Fi 网络')
    }
    
    // 5. 检查已配对设备
    const pairedDevices = await this.getPairedDevices()
    report.pairedCount = pairedDevices.length
    
    // 总结状态
    if (report.issues.length === 0) {
      report.status = 'healthy'
    } else if (report.issues.length <= 2) {
      report.status = 'warning'
    } else {
      report.status = 'critical'
    }
    
    return report
  }
  
  async checkPluginEnabled() {
    try {
      const config = await gateway({ action: 'config.get' })
      const plugin = config.plugins?.entries?.find(p => p.name === 'device-pair')
      return plugin?.enabled === true
    } catch (e) {
      return false
    }
  }
  
  async getPublicUrl() {
    try {
      const config = await gateway({ action: 'config.get' })
      return config.plugins?.entries?.find(p => p.name === 'device-pair')?.config?.publicUrl
    } catch (e) {
      return null
    }
  }
  
  async checkPortAccessibility() {
    try {
      const result = await exec({
        command: 'nc -z localhost 8080 && echo "open" || echo "closed"'
      })
      return result.stdout.trim() === 'open'
    } catch (e) {
      return false
    }
  }
  
  async checkLocalNetwork() {
    try {
      const result = await exec({
        command: 'ip addr show | grep "inet " | grep -v 127.0.0.1'
      })
      return result.stdout.trim().length > 0
    } catch (e) {
      return false
    }
  }
  
  async getPairedDevices() {
    try {
      const status = await nodes({ action: 'status' })
      return status.nodes || []
    } catch (e) {
      return []
    }
  }
  
  async generateReport() {
    const diagnosis = await this.check()
    
    let report = `# 设备配对诊断报告\n\n`
    report += `## 状态:${this.getStatusIcon(diagnosis.status)}\n\n`
    
    if (diagnosis.issues.length > 0) {
      report += `## ❌ 问题\n`
      for (const issue of diagnosis.issues) {
        report += `- ${issue}\n`
      }
      report += `\n`
    }
    
    if (diagnosis.recommendations.length > 0) {
      report += `## 💡 建议\n`
      for (const rec of diagnosis.recommendations) {
        report += `- ${rec}\n`
      }
      report += `\n`
    }
    
    report += `## 📊 统计\n`
    report += `- 已配对设备:${diagnosis.pairedCount || 0}\n`
    
    return report
  }
  
  getStatusIcon(status) {
    const icons = {
      healthy: '✅ 健康',
      warning: '⚠️ 警告',
      critical: '❌ 严重',
      unknown: '❓ 未知'
    }
    return icons[status] || status
  }
}

// 使用
const diagnostics = new PairingDiagnostics()
const report = await diagnostics.generateReport()
console.log(report)

三、设备管理

3.1 列出设备

javascript
// 获取所有节点状态
async function listNodes() {
  const status = await nodes({ action: 'status' })
  return status.nodes || []
}

// 获取设备详情
async function getNodeDetails(deviceId) {
  return await nodes({
    action: 'device_info',
    deviceId
  })
}

// 获取设备健康状态
async function getNodeHealth(deviceId) {
  return await nodes({
    action: 'device_health',
    deviceId
  })
}

// 获取设备权限
async function getNodePermissions(deviceId) {
  return await nodes({
    action: 'device_permissions',
    deviceId
  })
}

3.2 设备状态监控

javascript
class DeviceMonitor {
  constructor() {
    this.devices = new Map()
    this.alerts = []
  }
  
  async refresh() {
    const nodes = await listNodes()
    
    for (const node of nodes) {
      const info = await getNodeDetails(node.id)
      const health = await getNodeHealth(node.id)
      
      this.devices.set(node.id, {
        ...node,
        info,
        health,
        lastUpdate: Date.now()
      })
    }
    
    return this.devices
  }
  
  getStatusReport() {
    const devices = Array.from(this.devices.values())
    
    return {
      total: devices.length,
      online: devices.filter(d => d.connected).length,
      battery: devices.map(d => ({
        name: d.name || d.id,
        level: d.info?.battery?.level,
        charging: d.info?.battery?.charging,
        status: this.getBatteryStatus(d.info?.battery?.level)
      })),
      storage: devices.map(d => ({
        name: d.name || d.id,
        used: d.info?.storage?.used,
        total: d.info?.storage?.total
      }))
    }
  }
  
  getBatteryStatus(level) {
    if (!level) return 'unknown'
    if (level > 80) return 'excellent'
    if (level > 50) return 'good'
    if (level > 20) return 'low'
    return 'critical'
  }
  
  checkAlerts() {
    const alerts = []
    
    for (const [id, device] of this.devices) {
      // 低电量告警
      if (device.info?.battery?.level < 20) {
        alerts.push({
          type: 'low_battery',
          device: device.name || id,
          level: device.info.battery.level,
          severity: device.info.battery.level < 10 ? 'critical' : 'warning'
        })
      }
      
      // 存储空间告警
      if (device.info?.storage) {
        const usagePercent = (device.info.storage.used / device.info.storage.total) * 100
        if (usagePercent > 90) {
          alerts.push({
            type: 'low_storage',
            device: device.name || id,
            usage: usagePercent.toFixed(1),
            severity: 'warning'
          })
        }
      }
      
      // 离线告警
      if (!device.connected) {
        const offlineDuration = Date.now() - device.lastUpdate
        if (offlineDuration > 300000) {  // 5 分钟
          alerts.push({
            type: 'offline',
            device: device.name || id,
            duration: Math.round(offlineDuration / 60000),
            severity: 'warning'
          })
        }
      }
    }
    
    this.alerts = alerts
    return alerts
  }
  
  async sendAlerts() {
    for (const alert of this.alerts) {
      const message = this.formatAlert(alert)
      
      try {
        await message({
          action: 'send',
          target: '老大',
          message
        })
      } catch (e) {
        console.error('发送告警失败:', e)
      }
    }
    
    this.alerts = []
  }
  
  formatAlert(alert) {
    const icons = {
      low_battery: '🪫',
      low_storage: '💾',
      offline: '📴'
    }
    
    const severityIcons = {
      critical: '🚨',
      warning: '⚠️'
    }
    
    switch (alert.type) {
      case 'low_battery':
        return `${severityIcons[alert.severity]} ${icons.low_battery} 设备 ${alert.device} 电量过低:${alert.level}%`
      
      case 'low_storage':
        return `${severityIcons[alert.severity]} ${icons.low_storage} 设备 ${alert.device} 存储空间不足:${alert.usage}%`
      
      case 'offline':
        return `${severityIcons[alert.severity]} ${icons.offline} 设备 ${alert.device} 离线:${alert.duration}分钟`
      
      default:
        return `⚠️ 设备告警:${JSON.stringify(alert)}`
    }
  }
}

// 使用
const monitor = new DeviceMonitor()

// 定期监控
async function deviceMonitoringJob() {
  await monitor.refresh()
  const report = monitor.getStatusReport()
  
  console.log('设备状态报告:')
  console.log(`总设备:${report.total}`)
  console.log(`在线:${report.online}`)
  
  // 检查告警
  const alerts = monitor.checkAlerts()
  if (alerts.length > 0) {
    console.log(`发现 ${alerts.length} 个告警`)
    await monitor.sendAlerts()
  }
}

3.3 实战案例 2:设备批量管理

javascript
class DeviceManager {
  // 批量发送通知
  async broadcastNotification(title, body, options = {}) {
    const { priority = 'active', devices = null } = options
    
    const nodes = devices ? devices : await listNodes()
    const results = []
    
    for (const node of nodes) {
      try {
        await nodes({
          action: 'notify',
          deviceId: node.id,
          title,
          body,
          priority,
          delivery: options.delivery || 'system'
        })
        results.push({ device: node.id, success: true })
      } catch (error) {
        results.push({ device: node.id, success: false, error: error.message })
      }
    }
    
    return results
  }
  
  // 批量获取位置
  async getLocations(timeout = 10000) {
    const nodes = await listNodes()
    const results = []
    
    for (const node of nodes) {
      try {
        const location = await nodes({
          action: 'location_get',
          deviceId: node.id,
          desiredAccuracy: 'balanced',
          locationTimeoutMs: timeout
        })
        results.push({ device: node.id, location, success: true })
      } catch (error) {
        results.push({ device: node.id, success: false, error: error.message })
      }
    }
    
    return results
  }
  
  // 批量截取屏幕
  async captureScreens(options = {}) {
    const { durationMs = 5000, fps = 2 } = options
    
    const nodes = await listNodes()
    const results = []
    
    for (const node of nodes) {
      try {
        const screen = await nodes({
          action: 'screen_record',
          deviceId: node.id,
          durationMs,
          fps,
          includeAudio: options.includeAudio || false
        })
        results.push({ device: node.id, screen, success: true })
      } catch (error) {
        results.push({ device: node.id, success: false, error: error.message })
      }
    }
    
    return results
  }
  
  // 批量检查相机
  async checkCameras() {
    const nodes = await listNodes()
    const results = []
    
    for (const node of nodes) {
      try {
        // 列出相机
        const cameras = await nodes({
          action: 'camera_list',
          deviceId: node.id
        })
        
        // 拍摄照片
        const photo = await nodes({
          action: 'camera_snap',
          deviceId: node.id,
          facing: 'front'
        })
        
        results.push({
          device: node.id,
          cameras: cameras,
          photo: photo ? 'success' : 'failed',
          success: true
        })
      } catch (error) {
        results.push({ device: node.id, success: false, error: error.message })
      }
    }
    
    return results
  }
  
  // 生成设备报告
  async generateReport() {
    const nodes = await listNodes()
    
    let report = `# 设备管理报告\n\n`
    report += `生成时间:${new Date().toLocaleString('zh-CN')}\n\n`
    
    report += `## 设备列表\n\n`
    report += `| 设备 | 状态 | 电量 | 存储 | 位置 |\n`
    report += `|------|------|------|------|------|\n`
    
    for (const node of nodes) {
      const info = await getNodeDetails(node.id)
      
      const status = node.connected ? '🟢 在线' : '🔴 离线'
      const battery = info?.battery?.level ? `${info.battery.level}%` : 'N/A'
      const storage = info?.storage?.used 
        ? `${(info.storage.used / 1024 / 1024 / 1024).toFixed(1)}GB` 
        : 'N/A'
      const location = info?.location?.address || 'N/A'
      
      report += `| ${node.name || node.id} | ${status} | ${battery} | ${storage} | ${location} |\n`
    }
    
    return report
  }
}

// 使用
const manager = new DeviceManager()

// 批量通知
await manager.broadcastNotification(
  '系统通知',
  'OpenClaw 将进行例行维护,请保存好工作。',
  { priority: 'timeSensitive' }
)

// 生成报告
const report = await manager.generateReport()
console.log(report)

四、相机控制

4.1 相机操作

javascript
// 列出相机
async function listCameras(deviceId) {
  return await nodes({
    action: 'camera_list',
    deviceId
  })
}

// 拍摄照片
async function takePhoto(deviceId, options = {}) {
  return await nodes({
    action: 'camera_snap',
    deviceId,
    facing: options.facing || 'back',  // front | back | both
    quality: options.quality || 80,
    maxWidth: options.maxWidth || 1920
  })
}

// 录制视频
async function recordVideo(deviceId, options = {}) {
  return await nodes({
    action: 'camera_clip',
    deviceId,
    facing: options.facing || 'back',
    durationMs: options.durationMs || 10000,
    quality: options.quality || 70
  })
}

// 获取最新照片
async function getLatestPhotos(deviceId, limit = 5) {
  return await nodes({
    action: 'photos_latest',
    deviceId,
    limit
  })
}

4.2 实战案例 3:安全监控应用

javascript
class SecurityMonitor {
  constructor() {
    this.motionDetected = false
    this.recording = false
  }
  
  // 定时拍照监控
  async startMonitoring(deviceId, intervalMinutes = 5) {
    console.log(`启动安全监控,设备:${deviceId}`)
    
    setInterval(async () => {
      try {
        // 拍摄照片
        const photo = await takePhoto(deviceId, {
          facing: 'back',
          quality: 70
        })
        
        // 分析照片(使用 AI)
        const analysis = await sessions_spawn({
          task: `分析这张照片是否有异常情况:
          - 是否有人员出现
          - 是否有物品移动
          - 是否有异常光线变化
          
          返回 JSON: {"hasActivity": boolean, "description": "..."}`,
          attachments: [{
            name: 'monitoring.jpg',
            content: photo,
            encoding: 'base64'
          }],
          mode: 'run'
        })
        
        const result = JSON.parse(analysis.output)
        
        if (result.hasActivity) {
          console.log('⚠️ 检测到活动:', result.description)
          
          // 发送告警
          await message({
            action: 'send',
            target: '老大',
            message: `🚨 安全监控告警\n\n${result.description}\n时间:${new Date().toLocaleString('zh-CN')}`
          })
        }
        
      } catch (error) {
        console.error('监控失败:', error)
      }
    }, intervalMinutes * 60 * 1000)
  }
  
  // 检测到异常时录制视频
  async recordOnAlert(deviceId, durationSeconds = 30) {
    if (this.recording) {
      console.log('已在录制中')
      return
    }
    
    this.recording = true
    
    try {
      const video = await recordVideo(deviceId, {
        facing: 'back',
        durationMs: durationSeconds * 1000,
        quality: 70
      })
      
      // 保存视频
      await write({
        path: `/home/pao/.openclaw/workspace/security/alert-${Date.now()}.mp4`,
        content: video,
        encoding: 'base64'
      })
      
      console.log('✅ 视频已保存')
      
    } finally {
      this.recording = false
    }
  }
  
  // 远程查看
  async remoteView(deviceId) {
    // 拍摄当前画面
    const photo = await takePhoto(deviceId, {
      facing: 'back',
      quality: 90
    })
    
    // 获取位置
    const location = await nodes({
      action: 'location_get',
      deviceId
    })
    
    return {
      photo,
      location,
      timestamp: Date.now()
    }
  }
}

// 使用
const monitor = new SecurityMonitor()

// 启动监控
await monitor.startMonitoring('device-id-here', 5)

五、位置服务

5.1 位置获取

javascript
// 获取设备位置
async function getLocation(deviceId, options = {}) {
  return await nodes({
    action: 'location_get',
    deviceId,
    desiredAccuracy: options.accuracy || 'balanced',  // coarse | balanced | precise
    locationTimeoutMs: options.timeout || 10000
  })
}

// 解析位置信息
function parseLocation(locationData) {
  if (!locationData || !locationData.location) {
    return null
  }
  
  const loc = locationData.location
  return {
    latitude: loc.latitude,
    longitude: loc.longitude,
    accuracy: loc.accuracy,
    address: loc.address,
    timestamp: new Date(loc.timestamp).toLocaleString('zh-CN')
  }
}

5.2 实战案例 4:位置追踪应用

javascript
class LocationTracker {
  constructor() {
    this.history = new Map()
    this.geofences = []
  }
  
  // 添加地理围栏
  addGeofence(name, center, radiusMeters) {
    this.geofences.push({
      name,
      center: { lat: center.lat, lng: center.lng },
      radius: radiusMeters
    })
  }
  
  // 追踪设备位置
  async track(deviceId) {
    const location = await getLocation(deviceId)
    const parsed = parseLocation(location)
    
    if (!parsed) {
      console.log('无法获取位置')
      return null
    }
    
    // 保存历史
    if (!this.history.has(deviceId)) {
      this.history.set(deviceId, [])
    }
    
    this.history.get(deviceId).push({
      ...parsed,
      recordedAt: Date.now()
    })
    
    // 保留最近 100 条
    const history = this.history.get(deviceId)
    if (history.length > 100) {
      history.shift()
    }
    
    // 检查地理围栏
    const fenceAlerts = this.checkGeofences(parsed)
    
    return {
      location: parsed,
      geofenceAlerts: fenceAlerts
    }
  }
  
  checkGeofences(location) {
    const alerts = []
    
    for (const fence of this.geofences) {
      const distance = this.calculateDistance(
        location.latitude,
        location.longitude,
        fence.center.lat,
        fence.center.lng
      )
      
      if (distance <= fence.radius) {
        alerts.push({
          fence: fence.name,
          status: 'inside',
          distance: distance.toFixed(0)
        })
      } else {
        alerts.push({
          fence: fence.name,
          status: 'outside',
          distance: distance.toFixed(0)
        })
      }
    }
    
    return alerts
  }
  
  calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 6371000  // 地球半径(米)
    const dLat = this.toRad(lat2 - lat1)
    const dLon = this.toRad(lon2 - lon1)
    
    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
              Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) *
              Math.sin(dLon / 2) * Math.sin(dLon / 2)
    
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
    return R * c
  }
  
  toRad(deg) {
    return deg * Math.PI / 180
  }
  
  // 生成位置报告
  generateReport(deviceId) {
    const history = this.history.get(deviceId) || []
    
    if (history.length === 0) {
      return '无位置数据'
    }
    
    let report = `# 位置追踪报告\n\n`
    report += `设备:${deviceId}\n`
    report += `记录数:${history.length}\n\n`
    
    report += `## 最近位置\n`
    const latest = history[history.length - 1]
    report += `- 地址:${latest.address}\n`
    report += `- 坐标:${latest.latitude}, ${latest.longitude}\n`
    report += `- 精度:${latest.accuracy}米\n`
    report += `- 时间:${latest.timestamp}\n\n`
    
    report += `## 位置历史\n`
    report += `| 时间 | 地址 | 精度 |\n`
    report += `|------|------|------|\n`
    
    for (const record of history.slice(-10)) {
      report += `| ${record.timestamp} | ${record.address} | ${record.accuracy}m |\n`
    }
    
    return report
  }
}

// 使用
const tracker = new LocationTracker()

// 添加地理围栏
tracker.addGeofence('家', { lat: 39.9042, lng: 116.4074 }, 500)
tracker.addGeofence('公司', { lat: 39.9087, lng: 116.3975 }, 300)

// 追踪位置
const result = await tracker.track('device-id')
console.log(result)

// 生成报告
const report = tracker.generateReport('device-id')
console.log(report)

六、通知管理

6.1 通知操作

javascript
// 列出通知
async function listNotifications(deviceId) {
  return await nodes({
    action: 'notifications_list',
    deviceId
  })
}

// 发送通知
async function sendNotification(deviceId, options) {
  return await nodes({
    action: 'notify',
    deviceId,
    title: options.title,
    body: options.body,
    priority: options.priority || 'active',  // passive | active | timeSensitive
    delivery: options.delivery || 'system',  // system | overlay | auto
    sound: options.sound
  })
}

// 操作通知
async function actionNotification(deviceId, notificationKey, action) {
  return await nodes({
    action: 'notifications_action',
    deviceId,
    notificationKey,
    notificationAction: action,  // open | dismiss | reply
    notificationReplyText: action === 'reply' ? options.replyText : undefined
  })
}

6.2 实战案例 5:通知自动化

javascript
class NotificationAutomation {
  constructor() {
    this.rules = []
  }
  
  // 添加通知规则
  addRule(rule) {
    this.rules.push(rule)
  }
  
  // 监听并处理通知
  async processNotifications(deviceId) {
    const notifications = await listNotifications(deviceId)
    
    for (const notification of notifications.notifications || []) {
      // 检查规则
      for (const rule of this.rules) {
        if (this.matchRule(notification, rule)) {
          await this.executeRule(notification, rule)
          break
        }
      }
    }
  }
  
  matchRule(notification, rule) {
    // 匹配应用
    if (rule.app && notification.app !== rule.app) {
      return false
    }
    
    // 匹配标题关键词
    if (rule.titleContains && !notification.title.includes(rule.titleContains)) {
      return false
    }
    
    // 匹配内容关键词
    if (rule.bodyContains && !notification.body.includes(rule.bodyContains)) {
      return false
    }
    
    return true
  }
  
  async executeRule(notification, rule) {
    console.log(`执行规则:${rule.name}`)
    
    switch (rule.action) {
      case 'forward':
        // 转发到其他设备
        await this.forwardNotification(notification, rule.targets)
        break
      
      case 'log':
        // 记录到日志
        await this.logNotification(notification, rule.logFile)
        break
      
      case 'auto_reply':
        // 自动回复
        await this.autoReply(notification, rule.replyText)
        break
      
      case 'trigger':
        // 触发其他操作
        await this.triggerAction(notification, rule.trigger)
        break
    }
  }
  
  async forwardNotification(notification, targets) {
    for (const target of targets) {
      await sendNotification(target.deviceId, {
        title: `[转发] ${notification.title}`,
        body: notification.body,
        priority: 'active'
      })
    }
  }
  
  async logNotification(notification, logFile) {
    const entry = `[${new Date().toISOString()}] ${notification.app}: ${notification.title} - ${notification.body}\n`
    
    await exec({
      command: `echo "${entry}" >> ${logFile}`
    })
  }
  
  async autoReply(notification, replyText) {
    await actionNotification(notification.deviceId, notification.key, 'reply', {
      replyText
    })
  }
  
  async triggerAction(notification, trigger) {
    // 执行触发动作
    if (trigger.type === 'exec') {
      await exec({ command: trigger.command })
    } else if (trigger.type === 'message') {
      await message({
        action: 'send',
        target: trigger.target,
        message: trigger.message
      })
    }
  }
}

// 使用
const automation = new NotificationAutomation()

// 添加规则:转发微信消息
automation.addRule({
  name: '转发微信',
  app: '微信',
  titleContains: '消息',
  action: 'forward',
  targets: [{ deviceId: 'tablet-id' }]
})

// 添加规则:记录短信
automation.addRule({
  name: '记录短信',
  app: '信息',
  action: 'log',
  logFile: '/home/pao/.openclaw/logs/sms.log'
})

// 添加规则:验证码自动回复
automation.addRule({
  name: '验证码处理',
  titleContains: '验证码',
  action: 'auto_reply',
  replyText: '已收到'
})

// 处理通知
await automation.processNotifications('phone-id')

七、故障排查

7.1 常见问题

问题可能原因解决方案
配对失败网络不通检查防火墙、公网 URL
设备离线Wi-Fi 断开检查设备网络连接
位置获取失败权限未授予在设备上授予位置权限
相机不可用权限问题检查相机权限
通知不显示通知权限检查通知权限设置

7.2 诊断命令

bash
# 检查网关状态
openclaw gateway status

# 检查插件状态
openclaw plugins list

# 查看节点日志
openclaw nodes logs --follow

# 测试设备连接
openclaw nodes ping --device <device-id>

八、总结

核心要点

  1. 节点是连接物理设备的桥梁
  2. 配对配置决定连接方式
  3. 设备管理需要定期监控
  4. 相机、位置、通知是核心功能
  5. 安全监控是重要应用场景

进阶方向

  • 📱 开发自定义设备插件
  • 🏠 构建智能家居集成
  • 🚨 实现安全监控系统
  • 📍 开发位置服务应用

🟢🐉 开始管理你的设备节点吧!

Released under the MIT License.